import java.io.*;
import java.util.*;

class ParserException extends Exception
{
   ParserException(String s)
   {
      super(s);
   }
}

public class Interpreter
{
   ClassTable classTable = new ClassTable();
   VarTable varTable = new VarTable();

   Interpreter(String fileName) throws FileNotFoundException
   {
      System.out.println("-----------------------\nImage file: " + fileName);
      scanner = new Scanner(new FileInputStream(fileName));
   }

   void interpret() throws ParserException, BabytalkRuntimeException
   {
      if (!scanner.next().equals("\"Childtalk\""))
         throw new ParserException("Not a Childtalk image");

      // Parse class definitions
      System.out.println("------- Classes");

      String keyword = scanner.next();
      while (keyword.equals("class"))
      {
         Class clazz = loadClass();
			classTable.add(clazz);
         keyword = scanner.next();
      }

      // Similarly for var definitions
      System.out.println("------- Vars");
      while (keyword.equals("var"))
      {
         Var var = loadVar();
			varTable.add(var);
         System.out.println(var.getName() + ": " + var.getClassName());
         keyword = scanner.next();
      }
      
      System.out.println("------- Interpreting top-level invocations...");
		while (keyword.equals("invoke"))
      {
			Message message = loadMessage();
			interpret(message, varTable, "");
			keyword = scanner.next();
		}

      if (!keyword.equals("end"))
         throw new ParserException("Expected 'end', found '" + keyword + "'");

      if (scanner.hasNext())
         throw new ParserException("Expected end of source, found '" + scanner.next() + "'");
         
      System.out.println("-----------------------");
   }
   
   void interpret(Message message, VarTable varTable, String indent) throws BabytalkRuntimeException
   {
		Var var = varTable.get(message.receiver);
   	Class clazz = classTable.get(var.getClassName());

      while (clazz != null)
      {
         MethodTable methodTable = clazz.getMethodTable();
         String methodName = message.getMethodName();
         
         if (methodTable.containsKey(methodName))
         {
            Method method = clazz.getMethodTable().get(methodName);
            System.out.println(indent + "method: " + method.getName() + " invoked on " + var.getName() + "[" + var.getClassName() + "]");
            interpret(method, clazz.getVarTable(), indent);
            return;
         }
         
         clazz = clazz.getSubclass();
         if (clazz == null)
            throw new BabytalkRuntimeException("method " + methodName + " is not not defined in " + var.getClassName() + " or in subclasses");
      }
	}

	void interpret(Method method, VarTable varTable, String indent) throws BabytalkRuntimeException
   {
		for (Message message : method.getMessageTable()) 
			interpret(message, varTable, indent + "  ");
	}

   Class loadClass() throws ParserException, BabytalkRuntimeException
   {
		String name = scanner.next();
      System.out.println("class: " + name);
		Class clazz = new Class(name);
      String keyword = scanner.next();
      
		if (keyword.equals("subclass"))
      {
         String subclass = scanner.next();
         System.out.println("  subclass: " + subclass);
         clazz.setSubclass(classTable.get(subclass));
         keyword = scanner.next();
         if (!keyword.equals("{"))
            throw new ParserException("Missing '{' in class definition");
      }
      else if (!keyword.equals("{"))
      {
			throw new ParserException("Missing '{' or keyword subclass in class definition");
      }

      keyword = scanner.next();
		while (keyword.equals("var"))
      {
			Var var = loadVar();
			clazz.add(var);
         System.out.println("    " + var.getName() + ": " + var.getClassName());
			keyword = scanner.next();
		}
		while (keyword.equals("method"))
      {
			Method method = loadMethod();
			clazz.add(method);
         System.out.println("    " + method.getName() + "(" + Integer.toString(method.getMessageTable().size()) + " messages)");
			keyword = scanner.next();
		}
		if (!keyword.equals("}"))
			throw new ParserException("Missing '}' in class definition");
		return clazz;
	}

   Var loadVar() throws ParserException
   {
      String name = scanner.next();
      String separator = scanner.next();
      if (!separator.equals(":"))
         throw new ParserException("Missing ':' after 'var " + name + "' clause");

      String type = scanner.next();
		return new Var(name, type);
   }
   
	Method loadMethod() throws ParserException
   {
		String name = scanner.next();
		Method method = new Method(name);

		if (!scanner.next().equals("{"))
			throw new ParserException("Missing '{' in method definition");

		String keyword = scanner.next();
		while (keyword.equals("invoke"))
      {
         Message message = loadMessage();
         method.add(message);
         keyword = scanner.next();
		}
      
		if (!keyword.equals("}"))
			throw new ParserException("Missing '}' in method definition");

		return method;
	}

	Message loadMessage()
   {
      String receiver = scanner.next();
      String methodName = scanner.next();
		return new Message(receiver, methodName);
	}

   Scanner scanner;
}
